/*
 * Exercise-specific definitions of input and output data structures.
 * It also provide means to acquire input data and verify results.
 */
#ifndef _DATA_HPP
#define _DATA_HPP
#define _CRT_SECURE_NO_WARNINGS

#include "join_merge.hpp"
#include <vector>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>


/*
 * Structure representing all input data.
 */
struct DataIn
{
	std::vector<unsigned> set1, set2;

	/*
	 * Generate random values into both input vectors.
	 * The constructor recieves command line arguments (without program name
	 * and without arguments that belonged directly to the framework).
	 * Processed arguments should be consumed (so that argc is decremented and argv pointer advanced).
	 * Remaining arguments (that are not consumed) are passed to DataOut constructor.
	 */
	DataIn(int &argc, char **&argv)
	{
		if (argc == 0) {
			std::cerr << "No data file specified!" << std::endl;
			return;
		}

		FILE *fp = fopen(argv[0], "rb");
		if (fp == NULL) {
			std::cerr << "Cannot open data file!" << std::endl;
			return;
		}

		// Get data size
		unsigned size;
		fread(&size, sizeof(unsigned), 1, fp);
		set1.resize(size);
		set2.resize(size);

		// Load Set 1
		for (unsigned i = 0; i < size; ++i) {
			unsigned x;
			fread(&x, sizeof(unsigned), 1, fp);
			set1[i] = x;
		}
		
		// Load Set 2
		for (unsigned i = 0; i < size; ++i) {
			unsigned x;
			fread(&x, sizeof(unsigned), 1, fp);
			set2[i] = x;
		}
		
		fclose(fp);
	}

	DataIn(const DataIn &data) : set1(data.set1), set2(data.set2) {}
};


/*
 * Structure representing all output data.
 */
struct DataOut
{
private:
	std::vector<unsigned> correctRes;

public:
	std::vector<unsigned> res;

	/*
	 * Prepare data structures for computed results.
	 * The constructor recieves command line arguments (without program name
	 * and without arguments that belonged directly to the framework).
	 * Processed arguments should be consumed (so that argc is decremented and argv pointer advanced).
	 * Remaining arguments (that are not consumed) are passed to DataOut constructor.
	 */
	DataOut(int &argc, char **&argv)
	{
		res.reserve(16*1024*1024);
	}


	/*
	 * Erase previously acquired result.
	 */
	void clear()
	{
		res.clear();
		res.reserve(16*1024*1024);
	}


	/*
	 * Check whether the results has been computed corectly.
	 */
	bool checkResult(DataIn &dataIn)
	{
		if (correctRes.empty()) {
			std::vector<unsigned> set1, set2;
			set1.insert(set1.end(), dataIn.set1.begin(), dataIn.set1.end());
			set2.insert(set2.end(), dataIn.set2.begin(), dataIn.set2.end());

			mergeJoin(set1, set2, correctRes);
		}

		if (correctRes.size() != res.size())
			return false;

		std::sort(res.begin(), res.end());
		for (unsigned i = 0; i < res.size(); ++i)
			if (correctRes[i] != res[i]) return false;
		return true;
	}
};


/*
 * Program prototype class that contains also input and output data structures.
 * The implementation SHOULD be inherited directly from this class.
 */
class OCLProgram : public OCLProgram_Interface
{
protected:
	const DataIn &mDataIn;
	DataOut &mDataOut;

public:
	OCLProgram(const DataIn &dataIn, DataOut &dataOut) : OCLProgram_Interface(), mDataIn(dataIn), mDataOut(dataOut) {}
};

#endif
